إتقان بروتوكول Iterator في JavaScript. تعلم كيفية جعل أي كائن قابلاً للتكرار، والتحكم في حلقات `for...of`، وتنفيذ منطق تكرار مخصص لهياكل البيانات المعقدة بأمثلة عملية وواقعية.
إطلاق العنان للتكرار المخصص في JavaScript: نظرة معمقة على بروتوكول Iterator
التكرار هو أحد المفاهيم الأساسية في البرمجة. بدءًا من معالجة عناصر القائمة وصولًا إلى قراءة تدفقات البيانات، فإننا نعمل باستمرار مع تسلسلات المعلومات. في JavaScript، لدينا أدوات قوية وأنيقة مثل حلقة for...of وبنية الانتشار (...) التي تجعل التكرار على الأنواع المضمنة مثل المصفوفات والسلاسل النصية والخرائط تجربة سلسة.
ولكن هل توقفت يومًا وتساءلت عما الذي يجعل هذه الكائنات مميزة جدًا؟ لماذا يمكنك كتابة for (const char of "hello") ولكن ليس for (const prop of {a: 1, b: 2})؟ تكمن الإجابة في ميزة قوية، ولكن غالبًا ما يساء فهمها، في معيار ECMAScript: بروتوكول Iterator.
هذا البروتوكول ليس مجرد آلية داخلية للكائنات المضمنة في JavaScript. إنه معيار مفتوح، وهو عقد يمكن لأي كائن تبنيه. من خلال تنفيذ هذا البروتوكول، يمكنك تعليم JavaScript كيفية التكرار على الكائنات المخصصة الخاصة بك، مما يجعلها مواطنين من الدرجة الأولى في اللغة. يمكنك إطلاق العنان لنفس الأناقة النحوية لـ for...of لهياكل البيانات المخصصة الخاصة بك، سواء كانت شجرة ثنائية أو قائمة مرتبطة أو تسلسل دور لعبة أو جدول زمني للأحداث.
في هذا الدليل الشامل، سنزيل الغموض عن بروتوكول iterator. سنقوم بتقسيمه إلى مكوناته الأساسية، ونسير في بناء المكررات المخصصة من البداية، ونستكشف حالات الاستخدام المتقدمة مثل التسلسلات اللانهائية، وأخيرًا، نكتشف النهج الحديث والمبسط باستخدام وظائف المولدات. بحلول النهاية، لن تفهم فقط كيف يعمل التكرار تحت الغطاء، ولكنك أيضًا ستكون قادرًا على كتابة كود JavaScript أكثر تعبيرًا وقابلية لإعادة الاستخدام واصطلاحية.
جوهر التكرار: ما هو بروتوكول JavaScript Iterator؟
أولاً، من الضروري أن نفهم أن "بروتوكول iterator" ليس فئة واحدة تقوم بتوسيعها أو دالة معينة تستدعيها. إنها مجموعة من القواعد أو الاصطلاحات التي يجب أن يتبعها الكائن ليتم اعتباره "قابلاً للتكرار" ولإنتاج "iterator". من الأفضل التفكير فيه على أنه عقد. إذا قام الكائن الخاص بك بتوقيع هذا العقد، فإن محرك JavaScript يعد بمعرفة كيفية التكرار عليه.
ينقسم هذا العقد إلى جزأين متميزين:
- بروتوكول Iterable: يحدد هذا البروتوكول ما إذا كان الكائن قابلاً للتكرار في المقام الأول.
- بروتوكول Iterator: يحدد هذا البروتوكول آليات كيفية تكرار الكائن، قيمة واحدة في كل مرة.
دعنا نفحص كل جزء من هذا العقد بالتفصيل.
النصف الأول من العقد: بروتوكول Iterable
بروتوكول iterable بسيط بشكل مدهش. لديه شرط واحد فقط:
يعتبر الكائن قابلاً للتكرار إذا كان لديه خاصية محددة ومعروفة جيدًا توفر طريقة لاسترداد iterator. يتم الوصول إلى هذه الخاصية المعروفة باستخدام Symbol.iterator.
لذلك، لكي يكون الكائن قابلاً للتكرار، يجب أن يكون لديه طريقة يمكن الوصول إليها عبر المفتاح [Symbol.iterator]. عند استدعاء هذه الطريقة، يجب أن ترجع كائن iterator (والذي سنتناوله في القسم التالي).
قد تسأل، "ما هو Symbol، ولماذا لا نستخدم ببساطة اسم سلسلة مثل 'iterator'؟" Symbol هو نوع بيانات بدائي فريد وغير قابل للتغيير تم تقديمه في ES6. غرضه الأساسي هو أن يكون بمثابة مفتاح فريد لخصائص الكائن، ومنع الاصطدامات العرضية في الأسماء. إذا استخدم البروتوكول سلسلة بسيطة مثل 'iterator'، فقد يقوم التعليمات البرمجية الخاصة بك بتحديد خاصية بنفس الاسم لغرض مختلف، مما يؤدي إلى أخطاء غير متوقعة. باستخدام Symbol.iterator، يضمن مواصفات اللغة مفتاحًا فريدًا وموحدًا لا يتعارض مع التعليمات البرمجية الأخرى.
يمكننا بسهولة التحقق من ذلك على الكائنات القابلة للتكرار المضمنة:
const anArray = [1, 2, 3];
const aString = "global";
const aMap = new Map();
console.log(typeof anArray[Symbol.iterator]); // "function"
console.log(typeof aString[Symbol.iterator]); // "function"
console.log(typeof aMap[Symbol.iterator]); // "function"
// A plain object is not iterable by default
const anObject = { a: 1, b: 2 };
console.log(typeof anObject[Symbol.iterator]); // "undefined"
النصف الثاني من العقد: بروتوكول Iterator
بمجرد أن يثبت الكائن أنه قابل للتكرار من خلال توفير طريقة [Symbol.iterator]()، ينتقل التركيز إلى الكائن الذي ترجعه هذه الطريقة: iterator. iterator هو الحصان العامل الحقيقي؛ إنه الكائن الذي يدير فعليًا عملية التكرار وينتج تسلسل القيم.
بروتوكول iterator أيضًا واضح جدًا. لديه شرط واحد:
الكائن هو iterator إذا كان لديه طريقة تسمى next(). يجب أن ترجع هذه الطريقة next()، عند استدعائها، كائنًا بخصائص محددة:
done(منطقي): تشير هذه الخاصية إلى حالة التكرار. تكونfalseإذا كانت هناك المزيد من القيم القادمة في التسلسل. تصبحtrueبمجرد اكتمال التكرار.value(أي نوع): تحتوي هذه الخاصية على القيمة الحالية في التسلسل. عندما تكونdoneهيtrue، تكون الخاصيةvalueاختيارية وعادةً ما تحملundefined.
دعنا نلقي نظرة على iterator مستقل تم إنشاؤه يدويًا لنرى ذلك قيد التنفيذ، بشكل منفصل تمامًا عن أي كائن قابل للتكرار. سيقوم هذا iterator ببساطة بالعد من 1 إلى 3.
const manualCounterIterator = {
count: 1,
next: function() {
if (this.count <= 3) {
return { value: this.count++, done: false };
} else {
return { value: undefined, done: true };
}
}
};
// We call next() repeatedly to get each value
console.log(manualCounterIterator.next()); // { value: 1, done: false }
console.log(manualCounterIterator.next()); // { value: 2, done: false }
console.log(manualCounterIterator.next()); // { value: 3, done: false }
console.log(manualCounterIterator.next()); // { value: undefined, done: true }
console.log(manualCounterIterator.next()); // { value: undefined, done: true } - It stays done
هذه هي الآلية الأساسية التي تشغل كل حلقة for...of. عندما تكتب for (const item of iterable)، يقوم محرك JavaScript بما يلي وراء الكواليس:
- يستدعي الطريقة
[Symbol.iterator]()على كائنiterableللحصول على iterator. - ثم يستدعي بشكل متكرر الطريقة
next()على iterator. - لكل كائن تم إرجاعه حيث تكون
doneهيfalse، فإنه يعينvalueلمتغير الحلقة الخاص بك (item) وينفذ نص الحلقة. - عندما تُرجع
next()كائنًا حيث تكونdoneهيtrue، تنتهي الحلقة.
البناء من البداية: دليل عملي للتكرار المخصص
الآن بعد أن فهمنا النظرية، دعنا نضعها موضع التنفيذ. سنقوم بإنشاء فئة مخصصة تسمى Timeline. ستدير هذه الفئة مجموعة من الأحداث التاريخية، وهدفنا هو جعلها قابلة للتكرار مباشرة، مما يسمح لنا بالتكرار خلال الأحداث بترتيب زمني.
حالة الاستخدام: فئة `Timeline`
ستقوم فئتنا Timeline بتخزين الأحداث، كل منها كائن بـ year و description. نريد أن نكون قادرين على استخدام حلقة for...of للتكرار خلال هذه الأحداث، مرتبة حسب السنة.
class Timeline {
constructor() {
this.events = [];
}
addEvent(year, description) {
this.events.push({ year, description });
}
}
const myTimeline = new Timeline();
myTimeline.addEvent(1995, "JavaScript is created");
myTimeline.addEvent(2009, "Node.js is introduced");
myTimeline.addEvent(1997, "ECMAScript standard is first published");
myTimeline.addEvent(2015, "ES6 (ECMAScript 2015) is released");
// Goal: Make the following code work
// for (const event of myTimeline) {
// console.log(`${event.year}: ${event.description}`);
// }
تنفيذ خطوة بخطوة
لتحقيق هدفنا، نحتاج إلى تنفيذ بروتوكول iterator. هذا يعني إضافة الطريقة [Symbol.iterator]() إلى فئتنا Timeline.
تحتاج هذه الطريقة إلى إرجاع كائن جديد - iterator - والذي سيحتوي على الطريقة next() وإدارة حالة التكرار (على سبيل المثال، الحدث الذي نحن عليه حاليًا). إنه مبدأ تصميم حاسم يجب أن تعيش حالة التكرار على iterator، وليس الكائن القابل للتكرار نفسه. يسمح هذا بتكرارات متعددة ومستقلة على نفس الجدول الزمني في وقت واحد.
class Timeline {
constructor() {
this.events = [];
}
addEvent(year, description) {
// We'll add a simple check to ensure data integrity
if (typeof year !== 'number' || typeof description !== 'string') {
throw new Error("Invalid event data");
}
this.events.push({ year, description });
}
// Step 1: Implement the Iterable Protocol
[Symbol.iterator]() {
// Sort the events chronologically for iteration.
// We create a copy to not mutate the original array's order.
const sortedEvents = [...this.events].sort((a, b) => a.year - b.year);
let currentIndex = 0;
// Step 2: Return the iterator object
return {
// Step 3: Implement the Iterator Protocol with the next() method
next: () => { // Using an arrow function to capture `sortedEvents` and `currentIndex`
if (currentIndex < sortedEvents.length) {
// There are more events to iterate over
const currentEvent = sortedEvents[currentIndex];
currentIndex++;
return { value: currentEvent, done: false };
} else {
// We have reached the end of the events
return { value: undefined, done: true };
}
}
};
}
}
مشاهدة السحر: استخدام كائننا القابل للتكرار المخصص
مع تنفيذ البروتوكول بشكل صحيح، أصبح كائننا Timeline الآن قابلاً للتكرار بالكامل. يتكامل بسلاسة مع ميزات لغة JavaScript القائمة على التكرار. دعنا نراها قيد التنفيذ.
const myTimeline = new Timeline();
myTimeline.addEvent(1995, "JavaScript is created");
myTimeline.addEvent(2009, "Node.js is introduced");
myTimeline.addEvent(1997, "ECMAScript standard is first published");
myTimeline.addEvent(2015, "ES6 (ECMAScript 2015) is released");
console.log("--- Using for...of loop ---");
for (const event of myTimeline) {
console.log(`${event.year}: ${event.description}`);
}
// Output:
// 1995: JavaScript is created
// 1997: ECMAScript standard is first published
// 2009: Node.js is introduced
// 2015: ES6 (ECMAScript 2015) is released
console.log("\n--- Using spread syntax ---");
const eventsArray = [...myTimeline];
console.log(eventsArray);
// Output: An array of the event objects, sorted by year
console.log("\n--- Using Array.from() ---");
const eventsFrom = Array.from(myTimeline);
console.log(eventsFrom);
// Output: An array of the event objects, sorted by year
console.log("\n--- Using destructuring assignment ---");
const [firstEvent, secondEvent] = myTimeline;
console.log(firstEvent);
// Output: { year: 1995, description: 'JavaScript is created' }
console.log(secondEvent);
// Output: { year: 1997, description: 'ECMAScript standard is first published' }
هذه هي القوة الحقيقية للبروتوكول. من خلال الالتزام بعقد قياسي، جعلنا كائننا المخصص متوافقًا مع مجموعة واسعة من ميزات JavaScript الحالية والمستقبلية دون أي عمل إضافي.
النهوض بمهارات التكرار الخاصة بك
الآن بعد أن أتقنت الأساسيات، دعنا نستكشف بعض المفاهيم الأكثر تقدمًا التي تمنحك مزيدًا من التحكم والمرونة.
أهمية الحالة والمكررات المستقلة
في مثال Timeline الخاص بنا، كنا حريصين جدًا على وضع حالة التكرار (currentIndex ونسخة sortedEvents) داخل كائن iterator الذي تم إرجاعه بواسطة [Symbol.iterator](). لماذا هذا مهم جدا؟ لأنه يضمن أنه في كل مرة نبدأ فيها تكرارًا، نحصل على *iterator جديد ومستقل*.
يسمح هذا لمستهلكين متعددين بالتكرار على نفس الكائن القابل للتكرار دون التدخل في بعضهم البعض. تخيل إذا كانت currentIndex خاصية لمثيل Timeline نفسه - سيكون الأمر فوضى!
const sharedTimeline = new Timeline();
sharedTimeline.addEvent(1, 'Event A');
sharedTimeline.addEvent(2, 'Event B');
sharedTimeline.addEvent(3, 'Event C');
const iterator1 = sharedTimeline[Symbol.iterator]();
const iterator2 = sharedTimeline[Symbol.iterator]();
console.log(iterator1.next().value); // { year: 1, description: 'Event A' }
console.log(iterator2.next().value); // { year: 1, description: 'Event A' } (Starts its own iteration)
console.log(iterator1.next().value); // { year: 2, description: 'Event B' } (Unaffected by iterator2)
الانطلاق إلى اللانهاية: إنشاء تسلسلات لا نهاية لها
لا يتطلب بروتوكول iterator أن ينتهي التكرار على الإطلاق. يمكن أن تظل الخاصية done ببساطة false إلى الأبد. يتيح لنا ذلك نمذجة التسلسلات اللانهائية، والتي يمكن أن تكون مفيدة بشكل لا يصدق لمهام مثل إنشاء معرفات فريدة أو إنشاء تدفقات من البيانات العشوائية أو نمذجة التسلسلات الرياضية.
دعنا ننشئ iterator يقوم بإنشاء تسلسل فيبوناتشي إلى أجل غير مسمى.
const fibonacciSequence = {
[Symbol.iterator]() {
let a = 0, b = 1;
return {
next() {
[a, b] = [b, a + b];
return { value: a, done: false };
}
};
}
};
// We can't use spread syntax or Array.from() here, as that would create an infinite loop and crash!
// const fibArray = [...fibonacciSequence]; // DANGER: Infinite loop!
// We must consume it carefully, providing our own termination condition.
console.log("First 10 Fibonacci numbers:");
let count = 0;
for (const number of fibonacciSequence) {
console.log(number);
count++;
if (count >= 10) {
break; // It's crucial to break out of the loop!
}
}
طرق Iterator الاختيارية: `return()`
بالنسبة للسيناريوهات الأكثر تقدمًا، خاصة تلك التي تتضمن إدارة الموارد (مثل معالجة الملفات أو اتصالات الشبكة)، يمكن أن يكون لـ iterator اختياريًا طريقة return(). يتم استدعاء هذه الطريقة تلقائيًا بواسطة محرك JavaScript إذا توقف التكرار قبل الأوان. يمكن أن يحدث هذا إذا أنهى بيان `break` أو `return` أو `throw` حلقة `for...of` قبل اكتمالها.
يمنح هذا iterator فرصة لأداء مهام التنظيف.
function createResourceIterator() {
let resourceIsOpen = true;
console.log("Resource opened.");
let i = 0;
return {
next() {
if (i < 3) {
return { value: ++i, done: false };
} else {
console.log("Iterator finished naturally.");
resourceIsOpen = false;
console.log("Resource closed.");
return { done: true };
}
},
return() {
if (resourceIsOpen) {
console.log("Iterator terminated early. Closing resource.");
resourceIsOpen = false;
}
return { done: true }; // Must return a valid iterator result
}
};
}
console.log("--- Early exit scenario ---");
const resourceIterable = { [Symbol.iterator]: createResourceIterator };
for (const value of resourceIterable) {
console.log(`Processing value: ${value}`);
if (value > 1) {
break; // This will trigger the return() method
}
}
ملاحظة: توجد أيضًا طريقة throw() لنشر الأخطاء، ولكنها تستخدم بشكل أساسي في سياق وظائف المولدات، والتي سنناقشها بعد ذلك.
النهج الحديث: التبسيط باستخدام وظائف المولدات
كما رأينا، يتطلب التنفيذ اليدوي لبروتوكول iterator إدارة دقيقة للحالة وتعليمات برمجية أولية لإنشاء كائن iterator وإرجاع كائنات { value, done }. على الرغم من أنه من الضروري فهم هذه العملية، فقد قدم ES6 حلاً أكثر أناقة: وظائف المولدات.
وظيفة المولد هي نوع خاص من الوظائف يمكن إيقافه مؤقتًا واستئنافه، مما يسمح له بإنتاج سلسلة من القيم بمرور الوقت. إنه يبسط إنشاء المكررات بشكل كبير.
بناء الجملة الأساسي:
function*: تحدد العلامة النجمية وظيفة كمولد.yield: توقف هذه الكلمة الأساسية تنفيذ المولد و "تنتج" قيمة. عند استدعاء طريقةnext()الخاصة بـ iterator مرة أخرى، تستأنف الوظيفة من حيث توقفت.
عند استدعاء وظيفة مولد، فإنه لا ينفذ نصه على الفور. بدلاً من ذلك، فإنه يرجع كائن iterator متوافقًا تمامًا مع البروتوكول. يتعامل محرك JavaScript تلقائيًا مع آلة الحالة وطريقة next() وإنشاء كائنات { value, done } لك.
إعادة هيكلة مثال `Timeline` الخاص بنا
دعنا نرى كيف يمكن لوظائف المولدات أن تبسط بشكل كبير تنفيذ Timeline الخاص بنا. يظل المنطق كما هو، لكن الكود يصبح أكثر قابلية للقراءة وأقل عرضة للأخطاء.
class Timeline {
constructor() {
this.events = [];
}
addEvent(year, description) {
this.events.push({ year, description });
}
// Refactored with a generator function!
*[Symbol.iterator]() { // The asterisk makes this a generator method
// Create a sorted copy
const sortedEvents = [...this.events].sort((a, b) => a.year - b.year);
// Loop through the sorted events
for (const event of sortedEvents) {
// yield pauses the function and returns the value
yield event;
}
// When the function finishes, the iterator is automatically marked as 'done'
}
}
// Usage is exactly the same, but the implementation is cleaner!
const myGenTimeline = new Timeline();
myGenTimeline.addEvent(2002, "The Euro currency is introduced");
myGenTimeline.addEvent(1998, "Google is founded");
for (const event of myGenTimeline) {
console.log(`${event.year}: ${event.description}`);
}
انظر إلى الفرق! لقد اختفى الإنشاء اليدوي المعقد لكائن iterator. تتم إدارة الحالة (الحدث الذي نحن عليه) ضمنيًا بواسطة الحالة المتوقفة لوظيفة المولد. هذه هي الطريقة الحديثة والمفضلة لتنفيذ بروتوكول iterator.
قوة `yield*`
تتمتع وظائف المولد بقوة عظمى أخرى: yield* (yield star). يتيح ذلك للمولد تفويض عملية التكرار إلى كائن قابل للتكرار آخر. إنها أداة قوية بشكل لا يصدق لتكوين المكررات من مصادر متعددة.
تخيل أن لدينا فئة Project بها كائنات Timeline متعددة (على سبيل المثال، واحدة للتصميم، وواحدة للتطوير). يمكننا أن نجعل Project نفسه قابلاً للتكرار، وسوف يتكرر بسلاسة على جميع الأحداث من جميع جداولها الزمنية بالترتيب.
class Project {
constructor(name) {
this.name = name;
this.designTimeline = new Timeline();
this.devTimeline = new Timeline();
}
*[Symbol.iterator]() {
console.log(`Iterating through events for project: ${this.name}`);
console.log("--- Design Events ---");
yield* this.designTimeline; // Delegate to the design timeline's iterator
console.log("--- Development Events ---");
yield* this.devTimeline; // Then delegate to the dev timeline's iterator
}
}
const websiteProject = new Project("Global Website Relaunch");
websiteProject.designTimeline.addEvent(2023, "Initial wireframes created");
websiteProject.designTimeline.addEvent(2024, "Final brand guide approved");
websiteProject.devTimeline.addEvent(2024, "Backend API developed");
websiteProject.devTimeline.addEvent(2025, "Frontend deployment");
for (const event of websiteProject) {
console.log(` - ${event.year}: ${event.description}`);
}
الصورة الكبيرة: لماذا يعد بروتوكول Iterator حجر الزاوية في JavaScript الحديث
بروتوكول iterator هو أكثر بكثير من مجرد فضول أكاديمي أو ميزة لمؤلفي المكتبة. إنه نمط تصميم أساسي يعزز إمكانية التشغيل البيني والكود الأنيق. فكر فيه على أنه محول عالمي. من خلال جعل الكائنات الخاصة بك تتوافق مع هذا المعيار، يمكنك توصيلها بنظام بيئي ضخم من ميزات اللغة المصممة للعمل مع أي تسلسل من البيانات.
قائمة الميزات التي تعتمد على بروتوكول iterable واسعة ومتنامية:
- الحلقات:
for...of - إنشاء/تسلسل المصفوفة: بناء الانتشار (
[...iterable]) وArray.from(iterable) - هياكل البيانات: تقبل المنشئات الخاصة بـ
new Map(iterable)وnew Set(iterable)وnew WeakMap(iterable)وnew WeakSet(iterable)جميعها كائنات قابلة للتكرار. - العمليات غير المتزامنة: تعمل
Promise.all(iterable)وPromise.race(iterable)وPromise.any(iterable)على كائن iterable من Promises. - تفكيك البيانات: يمكنك استخدام تعيين تفكيك البيانات مع أي كائن iterable:
const [first, second] = myIterable; - واجهات برمجة التطبيقات الجديدة: واجهات برمجة التطبيقات الحديثة مثل
Intl.Segmenterلتقسيم النصوص ترجع أيضًا كائنات قابلة للتكرار.
عندما تجعل هياكل البيانات المخصصة الخاصة بك قابلة للتكرار، فإنك لا تقوم بتمكين حلقة for...of فحسب؛ بل تجعلها متوافقة مع هذه المجموعة القوية بأكملها من الأدوات، مما يضمن أن التعليمات البرمجية الخاصة بك متوافقة مع المستقبل وسهلة الاستخدام والفهم للمطورين الآخرين.
الخلاصة: خطواتك التالية في التكرار
لقد انطلقنا من القواعد الأساسية لبروتوكولات iterable و iterator إلى بناء المكررات المخصصة الخاصة بنا، وأخيرًا إلى بناء الجملة النظيف والحديث لوظائف المولدات. لديك الآن المعرفة لتعليم JavaScript كيفية اجتياز أي بنية بيانات يمكنك تخيلها.
يعد إتقان هذا البروتوكول خطوة مهمة في رحلتك كمطور JavaScript. ينقلك من كونك مستهلكًا لميزات اللغة إلى مبدع يمكنه توسيع الإمكانات الأساسية للغة لتناسب احتياجاتك الخاصة.
رؤى قابلة للتنفيذ للمطورين العالميين
- تدقيق التعليمات البرمجية الخاصة بك: ابحث عن الكائنات في مشاريعك الحالية التي تمثل تسلسلًا من البيانات. هل تقوم بالتكرار عليها باستخدام طرق مخصصة وغير قياسية مثل
.forEachItem()أو.getItems()؟ ضع في اعتبارك إعادة هيكلتها لتنفيذ بروتوكول iterator القياسي لتحسين إمكانية التشغيل البيني. - تبني الكسل: استخدم المكررات، وخاصة المولدات، لتمثيل مجموعات بيانات كبيرة أو حتى لا نهائية. يتيح لك ذلك معالجة البيانات عند الطلب، مما يؤدي إلى تحسينات كبيرة في كفاءة الذاكرة والأداء. أنت تحسب فقط ما تحتاجه، عندما تحتاجه.
- إعطاء الأولوية للمولدات: بالنسبة لأي كائن جديد تقوم بإنشائه يجب أن يكون قابلاً للتكرار، اجعل وظائف المولدات (
function*) هي خيارك الافتراضي. إنها أكثر إيجازًا وأقل عرضة لأخطاء إدارة الحالة وأكثر قابلية للقراءة من التنفيذ اليدوي. - فكر في التسلسلات: ابدأ في عرض مشاكل البرمجة من خلال عدسة التسلسلات. هل يمكن نمذجة عملية تجارية معقدة أو مسار تحويل البيانات أو انتقال حالة واجهة المستخدم على أنها سلسلة من الخطوات؟ إذا كان الأمر كذلك، فقد يكون iterator هو الأداة المثالية والأنيقة لهذه المهمة.
من خلال دمج بروتوكول iterator في مجموعة أدوات التطوير الخاصة بك، ستكتب كود JavaScript أنظف وأكثر قوة وأكثر تعبيرًا سيتم فهمه وتقديره من قبل المطورين في أي مكان في العالم.